package com.freestyle.common.utils;

import java.lang.reflect.Method;
import java.util.HashMap;


public class PrivateUtil {
  /**
   * 利用递归找一个类的指定方法，如果找不到，去父亲里面找直到最上层Object对象为止。
   *
   * @param clazz      目标类
   * @param methodName 方法名
   * @param classes    方法参数类型数组
   * @return 方法对象
   * @throws Exception
   */
  public static Method getMethod(Class clazz, String methodName,
                                 final Class[] classes) throws Exception {
    Method method = null;
    try {
      method = clazz.getDeclaredMethod(methodName, classes);
    } catch (NoSuchMethodException e) {
      try {
        method = clazz.getMethod(methodName, classes);
      } catch (NoSuchMethodException ex) {
        if (clazz.getSuperclass() == null) {
          return method;
        } else {
          method = getMethod(clazz.getSuperclass(), methodName,
                  classes);
        }
      }
    }
    return method;
  }

  /***
   * 返回对象指定哪个父类的方法
   * @param obj
   * @param parentClazz
   * @param methodName
   * @param classes
   * @return
   * @throws Exception
   */
  public static Method getMethod(Object obj,Class parentClazz, String methodName,
                                 final Class[] classes) throws Exception {
    Class lvClassz=obj.getClass();
    while (true){
      if (lvClassz==parentClazz) {
        return getMethod(lvClassz,methodName,classes);
      }
      lvClassz=lvClassz.getSuperclass();
    }
  }

  /**
   * @param obj        调整方法的对象
   * @param objects    参数数组
   * @return 方法的返回值
   */
  public static Object invoke(final Object obj, final Method method,
                               final Object[] objects) {
    try {
      method.setAccessible(true);// 调用private方法的关键一句话
      return method.invoke(obj, objects);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }


  /**
   * @param obj        调整方法的对象
   * @param methodName 方法名
   * @param classes    参数类型数组
   * @param objects    参数数组
   * @return 方法的返回值
   */
  public static Object invoke(final Object obj, final String methodName,
                              final Class[] classes, final Object[] objects) {
    try {
      Method method = getMethod(obj.getClass(), methodName, classes);
      return invoke(obj,method,objects);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public static Object invoke(final Object obj, final String methodName,
                              final Class[] classes) {
    return invoke(obj, methodName, classes, new Object[]{});
  }

  public static Object invoke(final Object obj, final String methodName) {
    return invoke(obj, methodName, new Class[]{}, new Object[]{});
  }

  static class A {
    private void printlnA(String s) {
      System.out.println(s);
    }
  }

  static class B extends A {
    private void printlnB() {
      System.out.println("b");
    }
  }
  static class MyMap extends HashMap<String,String>{
    public String put(String key, String value) {
      System.out.println("mymap put fired.");
      return  super.put(key,value);
    }
  }

    /**
     * 测试反射调用
     *
     * @param args
     */
    public static void main(String[] args) {
      MyMap lvMap=new MyMap();
      try {
        Method hash=getMethod(HashMap.class,"hash",new Class[]{Object.class});
        hash.setAccessible(true);
        int lvnHashCode= (int) hash.invoke(null,"key");
        //Method lvParentMethod=getMethod(lvMap,HashMap.class,"put",new Class[]{Object.class,Object.class});
        /*for (Method f:HashMap.class.getDeclaredMethods()){
           if (f.getName().equals("putVal")) {
             Class[] lvTypes = f.getParameterTypes();
             System.out.println(f.getName() + lvTypes);
           }
        }*/
        Method lvParentMethod=getMethod(HashMap.class,"putVal",new Class[]{int.class,Object.class,Object.class,boolean.class,boolean.class});
        lvParentMethod.setAccessible(true);
        String lvsRet= (String) PrivateUtil.invoke(lvMap,lvParentMethod,new Object[]{lvnHashCode,"key","true",false,true});
        /*MethodType mt=MethodType.methodType(String.class,String.class,String.class);
        MethodHandle mh= java.lang.invoke.MethodHandles.lookup().findSpecial(HashMap.class,
                "put",mt,lvMap.getClass() );
        String lvsRet= (String) mh.invoke(lvMap,"key","true");*/
        System.out.println(lvsRet);

      } catch (Throwable e) {
        e.printStackTrace();
      }
      System.out.println(lvMap);


      /*PrivateUtil.invoke(new B(), "printlnA", new Class[]{String.class},
              new Object[]{"test"});
      PrivateUtil.invoke(new B(), "printlnB");*/

    }

}